package com.jtalker.server;

import java.io.File;
import java.io.FilenameFilter;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * This Class makes all plugIn procedures: checks for available plugins and loads a needet one.
 */
public class JTalkerPlugInLoader {
	/** Last error message */
	private String lastErrorMsg = null;
	/** Array of JTalkerPlugInInterface objects */
	private JTalkerPlugInInterface allPlugins[] = null;
	/** PlugIn Directory */
	private String searchURL = null;
	/** Reference to the server instance */
	private JTalkerServer theServer = null;

	/**
	 * Constructor
	 * 
	 * @param theServer
	 *            JTalkerServer instance.
	 * @param searchURL
	 *            PlgIn directory. If null, the Loader tryes standard paths
	 */
	public JTalkerPlugInLoader(JTalkerServer theServer, String searchURL) {
		this.theServer = theServer;

		if (searchURL == null || searchURL.equals("")) {
			// Use default directory
			this.searchURL = "./plugins";
		} else {
			this.searchURL = searchURL;
		}
		String[] allPluginFiles = getPlugInNames(this.searchURL);
		if (allPluginFiles != null) {
			this.allPlugins = new JTalkerPlugInInterface[allPluginFiles.length];
		} else {
			// Disable plug-ins
			this.allPlugins = null;
			return;
		}

		try {
			// pre-load all PlugIns
			URL[] urlsToLoadFrom = new URL[] { new File(this.searchURL).toURI().toURL() };
			URLClassLoader loader = new URLClassLoader(urlsToLoadFrom);
			Class tmpClass = null;
			for (int i = 0; i < allPluginFiles.length; i++) {
				tmpClass = Class.forName("com.jtalker.server.plugins." + allPluginFiles[i], true, loader);
				this.allPlugins[i] = (JTalkerPlugInInterface) tmpClass.newInstance();
			}
		} catch (Exception e) {
			this.lastErrorMsg = "Error while initialize Plugins: " + e.toString();
			e.printStackTrace();

			// Disable plug-ins
			this.allPlugins = null;
		}
	}

	/**
	 * Gets qualified names of all loaded PlugIns as a String ";;"-separated
	 * 
	 * @return a string of available plugins e.g.: "pluginA;;pluginB;;pluginN"
	 */
	public String getLoadedPlugIns() {
		String tmpRetVal = "";
		if (this.allPlugins == null){
			return "";
		}
		int l = this.allPlugins.length;

		for (int i = 0; i < l; i++) {
			tmpRetVal += this.allPlugins[i].getClass().getName();
			if (i < (l - 1)){
				tmpRetVal += ";;";
			}
		}

		return tmpRetVal;
	}

	/**
	 * Parses and invokes a method if plugIn is available, otherwise it returns null
	 * 
	 * @param method
	 *            Method for a PlugIn. The String looks like this: plugin::method("parameter")
	 * @param caller
	 *            User who call this method
	 * @return The result of the method or null if something went wrong - in this case read the last error.
	 * @see getLastError()
	 */
	public synchronized String invokePlugInMethod(String method, JTalkerMember caller, String callerMsgID) {
		// Reset ErrorMsg
		this.lastErrorMsg = null;
		method = method.trim();

		try {
			if (method == null || method.indexOf("::") == -1) {
				this.lastErrorMsg = "Syntax error. Try module::method!";
				return null;
			}

			String[] tmpValues = method.split("::");

			if (tmpValues.length != 2) {
				this.lastErrorMsg = "Syntax error. Try module::method!";
				return null;
			}

			String plugin = tmpValues[0];
			String tempMethod = tmpValues[1];
			JTalkerPlugInInterface tmpPlugInObj = null;

			if (plugin == null || plugin.equals("") || tempMethod == null || tempMethod.equals("")) {
				this.lastErrorMsg = "Syntax error";
				return null;
			}

			// Stand-Server-Method?
			if (plugin.equalsIgnoreCase("std")) {
				return invokeBuildInMethod(tempMethod, caller);
			}

			if (this.allPlugins == null) {
				this.lastErrorMsg = "No plugin loaded.";
				return null;
			}

			// Check if plug-in is available
			boolean hit = false;

			for (int i = 0; i < this.allPlugins.length; i++) {
				System.out.println("--------this.allPlugins[i].getClass().getName()---------" + this.allPlugins[i].getClass().getName());
				if (plugin.equals(this.allPlugins[i].getClass().getName())) {
					hit = true;
					tmpPlugInObj = this.allPlugins[i];
					break;
				}
			}

			if (!hit) {
				this.lastErrorMsg = "Plugin is not available on this server: " + plugin;
				return null;
			}

			try {
				Method[] plugInsMethods = tmpPlugInObj.getMethods();

				tempMethod = tempMethod.trim();

				// Say PlugIn who's calling
				tmpPlugInObj.setCaller(caller.getUserName(), caller.getUserIP());

				if (tempMethod.equals("getName()")) {
					return tmpPlugInObj.getName();
				} else if (tempMethod.equals("getDescription()")) {
					return tmpPlugInObj.getDescription();
				} else if (tempMethod.equals("getRelease()")) {
					return tmpPlugInObj.getRelease();
				} else if (tempMethod.equals("getMethods()")) {
					String methodNamesInLine = "";
					for (int i = 0; i < tmpPlugInObj.getMethods().length; i++) {
						methodNamesInLine += tmpPlugInObj.getMethods()[i].toString();
						methodNamesInLine += ";;";
					}
					return methodNamesInLine;
				} else {
					// Parse and invoke special plug-in method
					this.theServer.getLogger().write(
							"+ Using Plugin: " + tmpPlugInObj.getName() + " caller: " + caller.getUserName());
					String tmpMethodName = (tempMethod.substring(0, tempMethod.indexOf('('))).trim();

					String[] params = this.parseParams(tempMethod);

					for (int i = 0; i < plugInsMethods.length; i++) {
						if (plugInsMethods[i].getName().equals(tmpMethodName)) {
							// HIT!
							String retVal = (String) plugInsMethods[i].invoke(tmpPlugInObj, params);

							// Reset callers data
							tmpPlugInObj.setCaller(null, null);

							return retVal;
						}
					}

					this.lastErrorMsg = "Method not declared: " + tempMethod;
					return null;
				}

				// DEBUG
				// System.out.println(plugInsMethods[0].invoke(tmpPlugInObj,
				// "10,12".split(",")));
				// retVal = (String)plugInsMethods[1].invoke(tmpPlugInObj,
				// null);

			} catch (Exception _e) {
				System.out.println("*** ERROR: Can't invoke PlugIn method (" + plugin + "::" + tempMethod + "): "
						+ _e.toString());
				this.lastErrorMsg = "Can't invoke method: " + tempMethod + ". " + _e.toString();
				return null;
			}

		} catch (Exception _e) {
			_e.printStackTrace();
			this.lastErrorMsg = "Can't invoke method: " + method + ". " + _e.toString();
		}

		this.lastErrorMsg = "Unexpected Error. Can't invoke this method";
		return null;
	}

	/**
	 * Gets an array of PlugIn names in given URL or null.
	 * 
	 * @param searchURL
	 *            PlugIn directory. If null, use "./plugins"
	 */
	public static String[] getPlugInNames(String searchURL) {
		if (searchURL == null) {
			searchURL = "./plugins";
		}
		File dir = new File(searchURL);

		FilenameFilter filter = new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.endsWith(".class");
			}
		};

		String plugins[] = dir.list(filter);

		if (plugins == null) {
			System.out.println("NULL");
			return null;
		}

		// Delete .class suffix
		for (int i = 0; i < plugins.length; i++) {
			plugins[i] = plugins[i].replaceAll(".class", "");
		}

		return plugins;
	}

	/**
	 * If an error occurs, this method gets an error string. Otherwise it returns null.
	 */
	public String getLastError() {
		String tmpRetVal = this.lastErrorMsg;
		this.lastErrorMsg = null;
		return tmpRetVal;
	}

	/**
	 * Invoke Buildin Method
	 * 
	 * @param method
	 *            Method to invoke
	 * @param caller
	 *            User who call this method
	 * @return Return value of the invoked method
	 * */
	private String invokeBuildInMethod(String method, JTalkerMember caller) {
		// remove spaces
		method = method.replaceAll(" ", "");

		if (method.equals("getAllUsers()")) {
			return this.theServer.getAllGlobalUsers();
		} else if (method.equals("getAllPlugIns()")) {
			return this.getLoadedPlugIns();
		} else if (method.equals("getRelease()")) {
			return JTalkerServer.RELEASE;
		} else if (method.equals("logout()")) {
			if (caller.closeSession()) {
				return "Session closed.";
			} else {
				return "Can't close this session. Maybe not webclient?";
			}
		} else if (method.startsWith("createChatRoom(")) {
			/** CREATE CHATROOM */
			String[] params = this.parseParams(method);
//			System.out.println("-------method------" + method);
//			for(String u : params){
//				System.out.println("-------params------" + u);
//			}
			if (params == null || params.length != 2) {
				return "Syntax error. Try: std::createChatRoom(\"roomName\", \"user1,user2,user3\")";
			}
			JTalkerMember member = this.theServer.addChatRoom(params[0]);
			if (member == null) {
				return "Can't create this chatroom.";
			}
			// Add the administrator and all users
			String callerName = caller.getUserName();
			member.setChatroomAdmin(callerName);
			String tmpUsers = params[1] + "," + callerName;
			member.setChatroomUsers(tmpUsers);

			this.theServer.sendUpdateRequestTo(tmpUsers);

			return "Chatroom \"" + params[0] + "\" successfull created (Op: " + member.getChatroomAdmin() + ")";

		} else if (method.startsWith("removeChatRoom(")) {
			// //// REMOVE CHATROOM /////////
			String[] params = this.parseParams(method);
			if (params == null || params.length != 1){
				return "Syntax error. Try: std::removeChatRoom(\"roomName\")";
			}
			if (!this.theServer.removeChatroom(params[0], caller)){
				return "Can't remove chatroom \"" + params[0] + "\". Wrong name or you are not the Operator!";
			} else {
				return "Chatroom removed.";
			}
		} else if (method.startsWith("addChatRoomUser(")) {
			// //// ADD CHATROOM USER /////////
			String[] params = this.parseParams(method);
			if (params == null || params.length != 2) {
				return "Syntax error. Try: std::addChatRoomUser(\"roomName\", \"userName\")";
			}
			if (!this.theServer.addChatroomUser(params[0], params[1], caller)) {
				return "Can't add user \"" + params[1] + "\". Wrong name or you are not the Operator!";
			} else {
				return "User added (" + params[1] + ").";
			}
		} else if (method.startsWith("removeChatRoomUser(")) {
			// //// REMOVE CHATROOM USER /////////
			String[] params = this.parseParams(method);
			if (params == null || params.length != 2){
				return "Syntax error. Try: std::addChatRoomUser(\"roomName\", \"userName\")";
			}
			if (!this.theServer.removeChatroomUser(params[0], params[1], caller)){
				return "Can't remove user \"" + params[1] + "\". Wrong name or you are not the Operator!";
			} else {
				return "User removed (" + params[1] + ").";
			}
		} else if (method.startsWith("getChatRoomUsers(")) {
			// //// GET CHATROOM USERS /////////
			String[] params = this.parseParams(method);
			if (params == null || params.length != 1) {
				return "Syntax error. Try: std::getChatRoomUsers(\"roomName\")";
			}
			return this.theServer.getChatroomUsers(params[0], caller);
		} else if (method.equals("getMethods()")) {
			// //// GET ALL BUILD-IN METHODS /////////
			String retVal = "std::getRelease();;std::getAllUsers();;std::getAllPlugIns();;std::logout();;";
			retVal += "std::createChatRoom(\"roomName\", \"user1,user2,user3\");;";
			retVal += "std::removeChatRoom(\"roomName\");;";
			retVal += "std::addChatRoomUser(\"roomName\", \"userName\");;";
			retVal += "std::removeChatRoomUser(\"roomName\", \"userName\");;";
			retVal += "std::getChatRoomUsers(\"roomName\");;";
			retVal += "<PLUGIN>::getName();;";
			retVal += "<PLUGIN>::getDescription();;";
			retVal += "<PLUGIN>::getRelease();;";
			retVal += "<PLUGIN>::getMethods();;";

			return retVal;
		}

		return "NOT IMPLEMENTED YET!";
	}

	/** Parsing method parameter */
	private String[] parseParams(String method) {
		String tmpParameters = method.substring(method.indexOf('(') + 1, method.lastIndexOf(')'));
		String[] params = tmpParameters.split("\"[\\s]*,[\\s]*\"");
		if (params == null || params.length < 1 || (params.length == 1 && params[0].equals(""))) {
			return null;
		} else {
			params[0] = params[0].replaceFirst("\"", "");
			params[params.length - 1] = params[params.length - 1].replaceFirst("\"", "");
		}

		return params;
	}

}
